﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using NetMicro.Cache.DelayBus;
using NetMicro.Core.Helper;
using NetMicro.EventBus.Diagnostics;
using NetMicro.EventBus.Extensions;
using NetMicro.EventBus.Messages;
using NetMicro.EventBus.Persistences;

namespace NetMicro.EventBus.Publishers
{
    public class DelayEventPublisher : IPublisherDelayEvent
    {
        public DelayEventPublisher(IMessageSerializer messageSerializer,
            IEventNameRuler eventNameRuler,
            IMsgIdGenerator msgIdGenerator,
            IOptions<EventBusOptions> options,
            IDelayMessageStorage delayMessageStorage)
        {
            MessageSerializer = messageSerializer;
            EventNameRuler = eventNameRuler;
            MsgIdGenerator = msgIdGenerator;
            Options = options;
            DelayMessageStorage = delayMessageStorage;
        }
        protected static readonly DiagnosticListener s_diagnosticListener =
         new DiagnosticListener(DiagnosticListenerNames.DiagnosticListenerName);
        private IMessageSerializer MessageSerializer { get; }


        private IEventNameRuler EventNameRuler { get; }

        private IMsgIdGenerator MsgIdGenerator { get; }

        private IOptions<EventBusOptions> Options { get; }

        private IDelayMessageStorage DelayMessageStorage { get; }
        public async Task PublishAsync<TEvent>(TEvent @event, TimeSpan delayAt, IDictionary<string, string> additionalItems = null, CancellationToken cancellationToken = default) where TEvent : IEvent
        {
            await Publish(@event, delayAt, additionalItems, cancellationToken).ConfigureAwait(false);
        }

        private async Task Publish<TEvent>(
           TEvent @event,
           TimeSpan delayAt,
           IDictionary<string, string>? additionalItems = null,
           CancellationToken cancellationToken = default
       )
        {
            if (@event is null) throw new ArgumentNullException(nameof(@event));


            var now = DateTimeOffset.Now;
            var eventName = EventNameRuler.GetName(typeof(TEvent));
            var msgId = MsgIdGenerator.GenerateId();
            additionalItems ??= new Dictionary<string, string>();
            additionalItems.Add(EventBusConsts.EventNameHeaderKey, eventName);
            additionalItems.Add(EventBusConsts.MsgIdHeaderKey, msgId);
            MessageTransfer messageTransfer = new MessageTransfer
            {
                MsgId = msgId,
                Version = Options.Value.Version,
                SendAt = now,
                EventName = EventNameRuler.GetName(typeof(TEvent)),
                Items = additionalItems,
                MsgBody = MessageSerializer.Serialize(@event)
            };
            long? tracingTimestamp = null;
            try
            {
                tracingTimestamp = TracingBefore(messageTransfer);
                var storeMessage = MessageSerializer.ConvertMessageStorageTransfer(messageTransfer, delayAt);
                await DelayMessageStorage.SaveAsync(storeMessage);
                TracingAfter(tracingTimestamp, messageTransfer);
            }
            catch (Exception ex)
            {
                TracingError(tracingTimestamp, messageTransfer, ex);
                throw;
            }
        }


        #region tracing
        private long? TracingBefore(MessageTransfer message)
        {
            if (s_diagnosticListener.IsEnabled(DiagnosticListenerNames.BeforePub))
            {
                var eventData = new TraceEventData()
                {
                    OperationTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
                    Operation = message.EventName,
                    Message = message
                };

                s_diagnosticListener.Write(DiagnosticListenerNames.BeforePub, eventData);
                return eventData.OperationTimestamp;
            }

            return null;
        }

        private void TracingAfter(long? tracingTimestamp, MessageTransfer message)
        {
            if (tracingTimestamp != null && s_diagnosticListener.IsEnabled(DiagnosticListenerNames.AfterPub))
            {
                var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                var eventData = new TraceEventData()
                {
                    OperationTimestamp = now,
                    Operation = message.EventName,
                    Message = message,
                    ElapsedTimeMs = now - tracingTimestamp.Value
                };

                s_diagnosticListener.Write(DiagnosticListenerNames.AfterPub, eventData);
            }
        }

        private void TracingError(long? tracingTimestamp, MessageTransfer message, Exception ex)
        {
            if (tracingTimestamp != null && s_diagnosticListener.IsEnabled(DiagnosticListenerNames.ErrorPub))
            {
                var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                var eventData = new TraceEventData()
                {
                    OperationTimestamp = now,
                    Operation = message.EventName,
                    Message = message,
                    ElapsedTimeMs = now - tracingTimestamp.Value,
                    Exception = ex
                };

                s_diagnosticListener.Write(DiagnosticListenerNames.ErrorPub, eventData);
            }
        }

        #endregion
    }
}
